Android APK的诞生记

在成为一个Android Developer后,其实一直都很好奇,一个.apk文件是怎么产生的,它是怎么从一堆.java、.xml等文件变成一个.apk的呢,随着知识体系的完善,这个问题貌似越来越清楚,但是一直未曾认认真真的梳理过一遍流程,恰好最近碰到相关问题,就借此机会总结一下,题其衣裳,以记其事~

环境准备

  • Mac OS
  • Java
  • Android SDK

准备项目

创建工程

直接使用Android Studio创建一个新的工程,比如:HelloAPK,这样创建出来的工程应该包含如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[min@TATEMIN-MB0:] HelloAPK $ ls -al
total 72
drwxr-xr-x 14 min staff 448 5 21 10:04 .
drwxr-xr-x 33 min staff 1056 7 10 10:22 ..
-rw-r--r-- 1 min staff 208 5 21 10:04 .gitignore
drwxr-xr-x 5 min staff 160 5 21 10:04 .gradle
drwxr-xr-x 10 min staff 320 5 21 13:53 .idea
-rw-r--r-- 1 min staff 862 5 21 10:04 HelloAPK.iml
drwxr-xr-x 9 min staff 288 5 21 10:06 app
-rw-r--r-- 1 min staff 564 5 21 10:04 build.gradle
drwxr-xr-x 3 min staff 96 5 21 10:04 gradle
-rw-r--r-- 1 min staff 1073 5 21 10:04 gradle.properties
-rwxr--r-- 1 min staff 5296 5 21 10:04 gradlew
-rw-r--r-- 1 min staff 2260 5 21 10:04 gradlew.bat
-rw-r--r-- 1 min staff 432 5 21 10:04 local.properties
-rw-r--r-- 1 min staff 43 5 21 10:04 settings.gradle

但是实际上决定APK的生成主要依赖于目录app/src/main,所以此目录就是我们编译APK的workspace。

修改工程

因为此次编译仅是向大家解释Android APK的生成过程,为了尽可能的方便,我们需要删除工程中的一些androidx的非Frameword的依赖,具体细节如下:

资源文件

  • 删除res/values/styles.xml

  • 修改res/layout/activity_main.xml文件:

    image-20200714192419028

代码文件

  • 修改java/com/min/helloapk/MainActivity.java文件

    image-20200714192652646

Manifest

  • 修改AndroidManifest.xml文件

    image-20200714192800708

编译项目

  • 生成签名

    1
    [min@TATEMIN-MB0:] main $ keytool -genkey -alias android.keystore -keyalg RSA -validity 1000 -keystore android.keystore
  • 查看签名

    1
    [min@TATEMIN-MB0:] main $ keytool -list -v -keystore android.keystore
  • 生成R.java

    1
    [min@TATEMIN-MB0:] main $ aapt package -v -f -m -S res/ -J java/ -M AndroidManifest.xml -I /Users/min/Library/Android/sdk/platforms/android-26/android.jar
  • 编译代码

    1
    2
    [min@TATEMIN-MB0:] main $ mkdir gen # 创建一个中间文件目录
    [min@TATEMIN-MB0:] main $ javac java/com/min/helloapk/*.java -verbose -classpath $ANDROID_HOME/platforms/android-26/android.jar -d gen

    此时会在bin目录下生成如下文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [min@TATEMIN-MB0:] main $ tree gen/
    gen/
    └── com
    └── min
    └── helloapk
    ├── MainActivity.class
    ├── R$attr.class
    ├── R$color.class
    ├── R$drawable.class
    ├── R$layout.class
    ├── R$mipmap.class
    ├── R$string.class
    └── R.class
  • 创建Dex文件

    1
    2
    [min@TATEMIN-MB0:] main $ mkdir bin
    [min@TATEMIN-MB0:] main $ dx --dex --verbose --output=bin/classes.dex gen

    此时会在bin目录下生成如下文件:

    1
    2
    3
    [min@TATEMIN-MB0:] main $ tree bin/
    bin/
    └── classes.dex
  • 创建APK文件

    1
    [min@TATEMIN-MB0:] main $ aapt package -v -f -M AndroidManifest.xml -S res -I $ANDROID_HOME/platforms/android-26/android.jar -F bin/HelloAPK.unsigned.apk bin

    此时会在bin目录下文件列表如下:

    1
    2
    3
    4
    [min@TATEMIN-MB0:] main $ tree bin/
    bin/
    ├── HelloAPK.unsigned.apk
    └── classes.dex
  • APK签名

    1
    [min@TATEMIN-MB0:] main $ jarsigner -verbose -keystore android.keystore -signedjar bin/HelloAPK.signed.apk bin/HelloAPK.unsigned.apk android.keystore
  • zipalign APK

    1
    zipalign -v -f 4 bin/HelloAPK.signed.apk bin/HelloAPK.apk
  • 安装APK

    1
    [min@TATEMIN-MB0:] main $ adb install bin/HelloAPK.apk
  • 运行APK

    image-20200714210745961

附录

如下是整个编译工程的Workspace:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
min@TATEMIN-MB0:] main $ tree
.
├── AndroidManifest.xml
├── android.keystore
├── bin
│   ├── HelloAPK.apk
│   ├── HelloAPK.signed.apk
│   ├── HelloAPK.unsigned.apk
│   └── classes.dex
├── gen
│   └── com
│   └── min
│   └── helloapk
│   ├── MainActivity.class
│   ├── R$attr.class
│   ├── R$color.class
│   ├── R$drawable.class
│   ├── R$layout.class
│   ├── R$mipmap.class
│   ├── R$string.class
│   └── R.class
├── java
│   └── com
│   └── min
│   └── helloapk
│   ├── MainActivity.java
│   └── R.java
└── res
├── drawable
│   └── ic_launcher_background.xml
├── drawable-v24
│   └── ic_launcher_foreground.xml
├── layout
│   └── activity_main.xml
├── mipmap-anydpi-v26
│   ├── ic_launcher.xml
│   └── ic_launcher_round.xml
├── mipmap-hdpi
│   ├── ic_launcher.png
│   └── ic_launcher_round.png
├── mipmap-mdpi
│   ├── ic_launcher.png
│   └── ic_launcher_round.png
├── mipmap-xhdpi
│   ├── ic_launcher.png
│   └── ic_launcher_round.png
├── mipmap-xxhdpi
│   ├── ic_launcher.png
│   └── ic_launcher_round.png
├── mipmap-xxxhdpi
│   ├── ic_launcher.png
│   └── ic_launcher_round.png
└── values
├── colors.xml
└── strings.xml

20 directories, 33 files